#include "public_includes.h"
#include "keyframe_list.h"
#include "keyframe.h"
#include "cex.h"
#include "private_includes.h"

typedef struct _vus_keyframe_list {
	uint count;
	vus_keyframe *keyframes;
} vus_keyframe_list;

#ifdef NDEBUG
#define invariant(self)
#else
#define invariant(self) \
	assert(self != NULL); \
	assert(self->count >= 0); \
	if (self->count > 0) \
		assert(self->keyframes == NULL); \
	else \
		assert(self->keyframes != NULL);
#endif

vus_keyframe_list *vus_keyframe_list_alloc() {
	vus_keyframe_list *output = (vus_keyframe_list *)malloc(sizeof(vus_keyframe_list));

	if (output == NULL)
		THROW(VUS_OUT_OF_MEMORY, "Out of memory.");

	output->count = 0;
	output->keyframes = NULL;

	invariant(output);
	return output;
}

void vus_keyframe_list_free(vus_keyframe_list *self) {
	assert(self != NULL);

	if (self->keyframes != NULL)
		free(self->keyframes);

	free(self);
}

void vus_keyframe_list_add(vus_keyframe_list *self, vus_keyframe value) {
	assert(self != NULL);

	++self->count;

	if (self->keyframes == NULL) // keyframes is null when empty - malloc for one item.
		self->keyframes = (vus_keyframe *)malloc(sizeof(vus_keyframe));
	else // otherwise, expand to the new size.
		self->keyframes = (vus_keyframe *)realloc(self->keyframes, self->count * sizeof(vus_keyframe));

	if (self->keyframes == NULL)
		THROW(VUS_OUT_OF_MEMORY, "Out of memory.");

	self->keyframes[self->count - 1] = value;

	invariant(self);
}

void vus_keyframe_list_remove(vus_keyframe_list *self, vus_keyframe value) {
	int index;
	assert(self != NULL);

	// Find the index of the item to be removed
	index = vus_keyframe_list_index_of(self, value);

	// -1 means that an item wasn't found, so exit here.
	if (index == -1)
		return;

	// Move all items to the right of the found value left one spot.
	if (index != self->count - 1) {
		memmove(
			&(self->keyframes[index]),
			&(self->keyframes[index + 1]),
			(self->count - index - 1) * sizeof(vus_keyframe));
	}

	// Reduce the size of the collection by one unit, or free/set to null if empty (to satisfy the invariant).
	if (--self->count > 0) {
		self->keyframes = (vus_keyframe *)realloc(self->keyframes, self->count * sizeof(vus_keyframe));
		THROW(VUS_OUT_OF_MEMORY, "Out of memory.");
	} else {
		free(self->keyframes);
		self->keyframes = 0;
	}

	invariant(self);
}

void vus_keyframe_list_clear(vus_keyframe_list *self) {
	assert(self != NULL);

	free(self->keyframes);
	self->keyframes = NULL;
	self->count = 0;

	invariant(self);
}

uint vus_keyframe_list_count(vus_keyframe_list *self) {
	assert(self != NULL);
	return self->count;
}

vus_keyframe vus_keyframe_list_get(vus_keyframe_list *self, uint index) {
	assert(self != NULL);
	assert(index >= 0);
	assert(index < self->count);

	return self->keyframes[index];
}

void vus_keyframe_list_set(vus_keyframe_list *self, uint index, vus_keyframe value) {
	assert(self != NULL);
	assert(index >= 0);
	assert(index < self->count);

	self->keyframes[index] = value;
	invariant(self);
}

int vus_keyframe_list_index_of(vus_keyframe_list *self, vus_keyframe value) {
	uint i = 0;
	assert(self != NULL);

	for (i = 0; i < self->count; i++)
		if (vus_keyframe_equals(self->keyframes[i], value))
			return i;

	return -1;
}
